﻿module IronLua.Runtime._Compiler

open System
open System.Linq.Expressions
open IronLua
open IronLua.Parsing
open IronLua.Parsing.Ast
// open IronLua.Runtime.DynamicLanguageRuntime

type internal FuncCompilationContext() = 
    class
    end

let islocal node =
    match node with
    | LocalDecl(p, lhs, rhs) -> true
    | LocalFunDecl(p, n, parms, body, upn, locn) -> true
    | _ -> false

let getlocdecls nodes : Node list =
    nodes |> List.filter islocal

let getnodes body =
    match body with
    | Block(range, nodes) -> nodes
    | _ as node -> LuaRuntimeException((sprintf "block expected, found %A" node)) |> raise

let rec compilestmt ctx thread threadparam closureparam stmt =
    match stmt with
    | Ast.Block(tp, blockstmts) ->                                  blockstmts |> compilestmts ctx thread threadparam closureparam
    | Ast.LocalDecl(tp, lexprlist, rexprlist) ->                    failwith "localdecl"
    | Ast.LocalFunDecl(tp, fname, fparms, block, fupn, flocn) ->    failwith "localfundecl"
    | Ast.FunDecl(tp, fpath, fname, fparms, block, fupn, flocn) ->  failwith "fundecl"
    | Ast.Call(tp, fnobj, args) ->                                  failwith "call"
    | Ast.Assign(tp, lexprlist, rexprlist) ->                       failwith "assign"
    | Ast.Return(tp, retvals) ->                                    failwith "return"
    | Ast.If(tp, cond, truepart, falsepart) ->                      failwith "if"
    | Ast.For(tp, iter, range, block) ->                            failwith "for"
    | Ast.GenericFor(tp, iters, range, block) ->                    failwith "genericfor"
    | Ast.Repeat(tp, block, cond) ->                                failwith "repeat"
    | Ast.While(tp, cond, block) ->                                 failwith "while"
    | Ast.Break(tp) ->                                              failwith "break"
    | Ast.Goto(tp, labelname) ->                                    failwith "goto"
    | Ast.Label(tp, labelname) ->                                   failwith "label"
    | _ ->                                          let stmtpos = (Parser.npos stmt)
                                                    LuaRuntimeException((sprintf "invalid statement at %i, %i" 
                                                                                 stmtpos.Line 
                                                                                 stmtpos.Column)) 
                                                    |> raise

and compilestmts ctx thread threadparam closureparam stmts =
    stmts 
    |> List.map (compilestmt ctx thread threadparam closureparam)
    |> List.toSeq
    |> Expression.Block
    :> Expression

let compileprotobody ctx thread tpos name parms stmts upn locn threadparam closureparam = 
    let luathread = Expression.Parameter(typeof<Thread>, "thread")
    let luaresult = Expression.Variable(typeof<LuaResult>, "result")
    let compiledbody = stmts |> compilestmts ctx thread threadparam closureparam
    let compiledcatch = luacatchall luaresult
    let compiledfinally = luafinally luathread luaresult
    block([ Expression.Assign(luaresult, Expression.Constant(LuaResult.OK));
            trycatchfinally compiledbody  
                            compiledcatch
                            compiledfinally ])
    
let compileproto ctx thread tpos name parms stmts upn locn = 
    let threadparam = param "thread" typeof<Thread>
    let closureparam = param "closure" typeof<Closure>
    let body = compileprotobody ctx thread tpos name parms stmts upn locn threadparam closureparam
    let lambda = lambda<LuaPrototype> body name [| threadparam; closureparam |]
    lambda.Compile()

let compileclosure ctx thread tpos name parms body upn locn = 
    let proto = compileproto ctx thread tpos name parms body upn locn
    LuaClosure(name, proto, locn, upn)

let compilechunk (thread : Thread) (topfn : Node) = 
    let fn = match topfn with 
             | FunDecl(tpos, path, name, parms, body, upn, locn) -> 
                                            let ctx = new FuncCompilationContext()
                                            let cl = compileclosure ctx thread tpos name parms (body |> getnodes) upn locn
                                            failwith "compile"
             | _ as node ->                 LuaRuntimeException((sprintf "function declaration expected, found %A" node)) |> raise
    failwith "compile"

(*

The statement 
     function f () body end
 translates to 
     f = function () body end

 The statement 
     function t.a.b.c.f () body end
 translates to 
     t.a.b.c.f = function () body end

 The statement 
     function t.a.b.c:f () body end
 translates to 
     t.a.b.c.f = function (self) body end
 called
     t.a.b.c:f() with self = t.a.b.c

 The statement 
     local function f () body end
 translates to 
     local f; f = function () body end

 The statement
     for v = e1, e2, e3 do block end
 is equivalent to the code: 
     do
       local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
       if not (var and limit and step) then error() end
       while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do
         local v = var
         block
         var = var + step
       end
     end

 A for statement like 
     for var_1, ···, var_n in explist do block end
 is equivalent to the code: 
     do
       local f, s, var = explist
       while true do
         local var_1, ···, var_n = f(s, var)
         if var_1 == nil then break end
         var = var_1
         block
       end
     end

----

    issues:
        coroutines: use clr threads
        stack: will use the lua stack (to support coroutines, variadic args and multiple return values) ?
        metamethods: will use callsites and binders ?
        errors: will use clr exceptions
        call stack: will use the lua call stack ?
        debug functions: will try and support where possible (and simulate otherwise)

----

    * values
        - some values may exhibit custom behaviour (table with metamethods); 
        - some tables may be created with weak keys, weak values, or both
        - everything is passed by value, references to heap objects as well

----

    * every chunk is actually a function and can return values
    * each function may contain other functions
        - we could flatten this hierarchy?
    * each function has local values (including params) and upvalues
        - params are taken from the stack and are then considered locals
    * each function can return 0 or more values
        - results are pushed on the stack
        - the number of results, if any, that are used by the caller depends on the context
            - might use no results
            - might use 1 result
            - might use all results
                - and if the caller expects more results than given, nils are added

----

    * lua state can contain all compiled chunk functions (prototypes?)
        - function instances (closures) will be assigned to values where necessary
    * dynamic call sites
        - every type will be given standard metatables
            - tables can have custom metamethods
        - each operation is turned into a lua function call with the correct context info
            - e.g. 
                    _ENV.meta = { __call = function(...) _ENV.print "called!" end };
                    _ENV.a = _ENV.setmetatable({ }, _ENV.meta);
                    _ENV.a(); --> called!

                    _ENV.print "called!"    call_binder(indexing_binder(_ENV, "print"), args: [ "called!" ]);
                    _ENV.a();               call_binder(indexing_binder(_ENV, "a"), args: []);

    * metamethods:              
        __index         event: indexer
        __newindex      event: adding a new field to a table
        __len           event: table len
        __eq            event: eq comparison
        __add           event: add binop
        __sub           event: sub binop
        __mul           event: mul binop
        __div           event: div binop
        __mod           event: mod unary
        __pow           event: pow binop
        __unm           event: unm unary
        __lt            event: lt comparison
        __le            event: le comparison
        __concat        event: concat binary
        __call          event: call

        __gc            destructor / finalizer
        __mode          table mode
        __pairs         used by tables
        __ipairs        used by tables
                    
----
    
    * scopes

    chunk.lua                   _ENV["@chunk.lua"] = function()

    local a = 10                                        local a = 10    

    function x()                                        _ENV.x = function()                                        
        return a * 2;                                               return a * 2;
    end                                                          end
                                                     end

----

compilation example.

source:
    g1 = 10
    local x = 3;

    local function testfn(p1, p2)
	    return g1 + p1 + p2 + x;
    end

    local x = testfn(1, 2);
    print(x);
    return x;

transformed source:

    _ENV = *host-given*

    function(...) 
        _ENV["x"] = 10;
        local x = 3;

        local testfn = function(p1, p2)
                           return _ENV["g1"] + p1 + p2 + x;
                       end

        local x = testfn(1, 2);
        _ENV["print"](x);
        return x;
    end

compiled:

    @ctest.lua_proto = (thread, closure) => {
        bool __$luafnfailed = false;
        try
        {
            dynamic(setmember, scope[0].upvalues[0], "x", 10);
            scope.locals[0] = 3;

            scope.locals[1] = new LuaClosure(testfn_proto, scope);

            thread.stack.push(1);
            thread.stack.push(2);

            scope.locals[2] = dynamic(call, scope.locals[1]);

            thread.stack.push(scope.locals[2]);

            dynamic(call, dynamic(index, scope[0].upvalues[0], "print"));

            return;
        }
        catch(LuaException ex)
        {
            __$luafnfailed = true;
            throw;
        }
        finally
        {
            if(__$luafnfailed) {
                // signal thread status
            }
        }
    };

    testfn_proto = (thread, closure) => {
        bool __$luafnfailed = false;
        try
        {
            // return _ENV["g1"] + p1 + p2 + x;
            thread.stack.push(
                dynamic(add, 
                    dynamic(index, scope[0].upvalues[0], "g1"),
                    dynamic(add, 
                        scope.locals[0],
                        dynamic(add, 
                            scope.locals[1],
                            scope.upvalues[0]))));
                        
            return;
        }
        catch(LuaException ex)
        {
            __$luafnfailed = true;
            throw;
        }
        finally
        {
            if(__$luafnfailed) {
                // signal thread status
            }
        }
    }

*)